home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / add next >
Encoding:
AWK Script  |  1997-08-26  |  54.2 KB  |  1,590 lines

  1. #!/usr/local/bin/gawk -f
  2. # @(#) add.gawk 3.2 97/02/28
  3. # 1988,89  john h. dubois iii (john@armory.com)
  4. # 90/04/23 last DOS version
  5. # 92/05/01 changed to be a #!awk script
  6. # 93/06/07 Exit if bad field number.  Leave default FS alone.
  7. # 95/01/29 Allow negative offsets.  Understand tie fields.  Added all options.
  8. # 95/02/03 Do some error checking; added a option; allow - field offsets.
  9. # 95/06/18 Added M option.
  10. # 95/06/18 Let comment string be given in selected field too.
  11. # 95/08/26 Added n option
  12. # 96/02/21 Print out entire line on error.
  13. # 96/07/05 Added R option.
  14. # 96/11/24 Added q%HpPi options.
  15. # 96/12/05 Added x option.  Made F option work.
  16. # 96/12/13 Added Cs options.
  17. # 96/12/30 Added dID option.
  18. # 97/01/26 Let aM be given with -q.  Changed R to S; made S & l not imply r
  19. #          if q is given.  Added run length counting.
  20. # 97/02/11 Changed M to e.  Added NMkE options.  Use %.18g for output.
  21. # 97/02/26 Print # of different values in q report header.
  22. # 97/02/28 Added o option.
  23.  
  24. BEGIN {
  25.     Name = "add"
  26.     Usage = "Usage: " Name \
  27. " [-adDeEhHiIkMnNqrRsStT%] [-[pP]<pattern>] [-c<comment-string>]\n"\
  28. " [-o<format>] [-l<lines>] [-F<field-sep>] [-C<min-count>] field-num [file ...]"
  29.     ARGC = Opts(Name,Usage,"aF:c:eEnNl>tqThrSMk&#%Hp;P;iIx>C>sdDRo:",
  30.     0,"","",0,"",0,"","t,T,R,NMk;q,hnrtTRNMk;M,k;T,E,o")
  31.     maxDig = 18
  32.     numFormat = "%." maxDig "g"
  33.     if ("h" in Options) {
  34.     printf \
  35. "%s: add a column of numbers.\n"\
  36. "%s\n"\
  37. "%s adds up the numbers found in the specified field number of the\n"\
  38. "input, and prints the result.  If no input files are given, the standard\n"\
  39. "input is read.  If a field number is prefixed by '-', the field number is\n"\
  40. "taken to be an offset from the last field on the line.  So, a field number\n"\
  41. "of '-0' refers to the last field, -1 refers to the second to last, etc.\n"\
  42. "Options:\n"\
  43. "-a: Print an average of the values found or, if -q is given, of the counts.\n"\
  44. "-h: Print this help.\n"\
  45. "-S: Print only as many lines as will fit on the screen: the last lines\n"\
  46. "    processed along with the total, or if -q is given, first lines of the\n"\
  47. "    report.  Lines are truncated to the screen width.  If -q is not given,\n"\
  48. "    this turns on the r option.\n"\
  49. "-l<lines>: Like -S, but prints the last or first <lines> lines of output\n"\
  50. "    without truncating to the screen width.\n"\
  51. "-c<comment-string>: Set the comment prefix to be <comment-string>.  Any\n"\
  52. "    input lines that have this string either at the start of the line or\n"\
  53. "    at the start of the selected field will be ignored.  The default is\n"\
  54. "    '#'.  To have no string be the comment string, use '-c \"\"'\n"\
  55. "-F<field-sep>: Set the field separator to the pattern <field-sep>.  The\n"\
  56. "    default is one or more spaces or tabs, with an initial string of\n"\
  57. "    spaces and tabs not counted as a separator.\n"\
  58. "-M: If a value has a metric suffix attached, it is used to scale the\n"\
  59. "    number.  E.g. 2.35K becomes 2350.  The suffix may be separated from\n"\
  60. "    from the value by whitespace.  If units are also attached (e.g. 2KB),\n"\
  61. "    the -N option should also be given so that the units are discarded.\n"\
  62. "    However, a unit name that begins with a character that is also a\n"\
  63. "    metric suffix will cause an incorrect value to be produced if any\n"\
  64. "    fields that have no metric suffix are read.\n"\
  65. "    The metric suffixes are: y z f a p n u m c d (none) D H K M G T P E Z Y\n"\
  66. "    Due to common usage, k is recognized as a synonym for K.\n"\
  67. "-k: Like -M, but the \"computer\" (power-of-two) values for the metric\n"\
  68. "    suffixes are used; e.g. K = 1024.  The c, d, D, and H suffixes are not\n"\
  69. "    recognized.\n"\
  70. "-N: Strip non-numeric characters from the selected field before using it.\n"\
  71. "    The first string in the field that looks like a numeric value is used.\n"\
  72. "    Numeric values are strings of digits with optional decimal point and\n"\
  73. "    leading minus sign.  If -M or -k is also given, the appropriate\n"\
  74. "    suffixes are also included as part of the possible values.\n"\
  75. "-R: The values counted are run lengths: the number of successive lines on\n"\
  76. "    which the selected field is identical.\n"\
  77. "-q: Instead of adding the values that appear in each field, keep a count\n"\
  78. "    of the number of times each unique field value appears, and print a\n"\
  79. "    frequency breakdown after all lines have been read.  Use -F \"\" to\n"\
  80. "    treat the entire line as field 1.\n"\
  81. "-e: Print extremes of values (minimum and maximum) that occur in the sum\n"\
  82. "    during processing.  Unless the values include negative numbers, this\n"\
  83. "    will be 0 and the sum of all values.  If -q is given, the minimum and\n"\
  84. "    maximum counts are printed.  If -R is given, the minimum and maximum\n"\
  85. "    run lengths are printed.\n"\
  86. "-E: Print output in \"engineering notation\".  In this notation, there are\n"\
  87. "    always between one and three digits before the decimal point.  A metric\n"\
  88. "    suffix is attached to retain the original value.  The -k option\n"\
  89. "    affects this option by changing the values of the suffixes, as\n"\
  90. "    described for -k.\n"\
  91. "-o<format>: Print numbers in <format>, which should be a printf-style\n"\
  92. "    format string.  The default is \"%s\".\n"\
  93. "The following options can not be given with -q:\n"\
  94. "-n: Do not print 'Total: ' in front of the total.\n"\
  95. "-[tT]: Add up the field as a time.  Fields should have the form hh:mm:ss\n"\
  96. "    or mm:ss or hh:mm.  If any fields of the form hh:mm:ss are found, the\n"\
  97. "    total is printed in the form [<days>d]hh:mm:ss and any fields of the\n"\
  98. "    form nn:nn are interpreted as hours:minutes.  Otherwise, the total is\n"\
  99. "    printed in the form nn:nn.  To force the first form of total, and\n"\
  100. "    cause nn:nn fields to be interpreted as hours:minutes, use -T instead\n"\
  101. "    of -t.\n"\
  102. "-r: Print a running total before each line.\n"\
  103. "The following options apply only if -q is given:\n"\
  104. "-C<min-count>: Print only values that occur at least <min-count> times.\n"\
  105. "-i: Ignore case.  Word counts will be printed in lower case.\n"\
  106. "-I: Like -i, except that counts are printed with whatever capitalization\n"\
  107. "    the first instance of each word has.\n"\
  108. "-d: Dictionary order.  Everything except letters, digits, and blanks\n"\
  109. "    (spaces and tabs) are removed before comparision.  Leading and\n"\
  110. "    trailing blanks are removed, and sequences of blanks are squeezed to a\n"\
  111. "    single space character.  The dictionary (\"squeezed\") version of each\n"\
  112. "    word is used in the output.\n"\
  113. "-D: Like -d, except that counts are printed using the unsqueezed version\n"\
  114. "    of the first instance of each word.\n"\
  115. "-s: Conglomerate plural and singular field values.  If a field exists\n"\
  116. "    both with and without a trailing s, those with the trailing s are \n"\
  117. "    included in the count without the trailing s.\n"\
  118. "-%: Include a percentage of total field in the output.\n"\
  119. "-H: Do not include a header.\n"\
  120. "-p<pattern>: Count only fields that match <pattern>.\n"\
  121. "-P<pattern>: Count only fields that do not match <pattern>.\n",
  122.     Name,Usage,Name,numFormat
  123.     Err = "0"
  124.     exit 0
  125.     }
  126.     if ("x" in Options)
  127.     Debug = Options["x"]
  128.     if ("o" in Options)
  129.     numFormat = Options["o"]
  130.     if ("C" in Options)
  131.     minCount = Options["C"]
  132.     if ("&" in Options)
  133.     fieldnum = -Options["&"]
  134.     else {
  135.     if (ARGC < 2) {
  136.         print Name ": Not enough arguments.  Use -h for help." \
  137.         > "/dev/stderr"
  138.         Err = 1
  139.         exit 1
  140.     }
  141.     fieldnum = ARGV[1]
  142.     RFieldnum = fieldnum
  143.     if (fieldnum !~ /^[0-9]+$/ || fieldnum < 1) {
  144.         print Name ": bad field number." > "/dev/stderr"
  145.         Err = 1
  146.         exit 1
  147.     }
  148.     if (ARGC > 2)
  149.         ARGV[1] = "/dev/null"
  150.     else
  151.         ARGV[1] = "/dev/stdin"
  152.     }
  153.     if (Debug)
  154.     printf "Counting field number: %d\n",fieldnum
  155.     # This is the same as awk's -F option.  If this option is given first,
  156.     # awk will take care of it; if it comes after a non-awk option, it will
  157.     # appear in Options.
  158.     if ("F" in Options)
  159.     FS = Options["F"]
  160.     # gawk does not do what we want in this case (awk does); make FS be RS
  161.     # so line will never be split.
  162.     if (FS == "")
  163.     FS = RS
  164.     Freq = "q" in Options
  165.     if ("p" in Options)
  166.     pattern = Options["p"]
  167.     if ("P" in Options)
  168.     notPattern = Options["P"]
  169.     origCase = "I" in Options
  170.     IGNORECASE = "i" in Options || origCase
  171.     origDict = "D" in Options
  172.     Dictionary = "d" in Options || origDict
  173.     saveValue = origCase || origDict
  174.     Running = ("r" in Options) || !Freq && ("l" in Options || "S" in Options)
  175.     DoHours = "T" in Options
  176.     MinMax = "e" in Options
  177.     Pow2 = "k" in Options
  178.     Eng = "E" in Options
  179.     if (Metric = ("M" in Options || Pow2)) {
  180.     split("K M G T P E Z Y",elem," ")
  181.     n = 1
  182.     for (i = 1; i in elem; i++)
  183.         metricChars[elem[i]] = n *= (Pow2 ? 1024 : 1000)
  184.     split("m u n p a f z y",elem," ")
  185.     n = 1
  186.     for (i = 1; i in elem; i++)
  187.         metricChars[elem[i]] = n /= (Pow2 ? 1024 : 1000)
  188.     metricChars["k"] = metricChars["K"]
  189.     if (!Pow2) {
  190.         metricChars["c"] = 0.01
  191.         metricChars["d"] = 0.1
  192.         metricChars["D"] = 10
  193.         metricChars["H"] = 100
  194.     }
  195.     }
  196.     Average = "a" in Options
  197.     pl_sing = "s" in Options
  198.     RunLen = "R" in Options
  199.     if (StripNum = "N" in Options) {
  200.     numPat = "-?([0-9]+\\.?[0-9]*|\\.[0-9]+)"
  201.     if (Pow2)
  202.         numPat = numPat "[yzfapnumkKMGTPEZY]?"
  203.     else if (Metric)
  204.         numPat = numPat "[yzfapnumcdDHkKMGTPEZY]?"
  205.     if (Debug)
  206.         printf "Number pattern: %s\n",numPat > "/dev/stderr"
  207.     }
  208.     DoTime = DoHours || "t" in Options
  209.     if ("c" in Options) {
  210.     if (Options["c"] != "")
  211.         Comment = "^" Options["c"]
  212.     }
  213.     else
  214.     Comment = "^#"
  215.     if ("S" in Options)
  216.     HeadTailInit()
  217.     else if ("l" in Options)
  218.     HeadTailInit(Options["l"],-1,0)
  219.     else
  220.     HeadTailInit(-1,-1)
  221. }
  222.  
  223. Comment != "" && $1 ~ Comment {
  224.     if (Running && !(Freq || RunLen))
  225.     TailPrint(sprintf("%s\t%s",FormatTotal(),$0))
  226.     if (runLen)
  227.     doRunLen("",1,NR) 
  228.     next
  229. }
  230.  
  231. {
  232.     if (fieldnum < 1) {
  233.     if (NF < (1-fieldnum)) {
  234.         printf "Not enough fields on line %d of file %s\n",FNR,
  235.         FILENAME > "/dev/stderr"
  236.         if (runLen)
  237.         doRunLen("",1,NR) 
  238.         next
  239.     }
  240.     RFieldnum = NF + fieldnum
  241.     }
  242.     else {
  243.     if (NF < fieldnum) {
  244.         printf "Not enough fields on line %d of file %s\n",FNR,
  245.         FILENAME > "/dev/stderr"
  246.         if (runLen)
  247.         doRunLen("",1,NR) 
  248.         next
  249.     }
  250.     }
  251.     Val = $RFieldnum
  252.     if (Val ~ Comment) {
  253.     if (Running && !(Freq || RunLen))
  254.         TailPrint(sprintf("%s\t%s",FormatTotal(),$0))
  255.     if (runLen)
  256.         doRunLen(Val,0,NR) 
  257.     next
  258.     }
  259.     if (Val ~ /^[ \t]*$/) {
  260.     printf "Empty field %d on line %d of file %s\n",fieldnum,FNR,
  261.     FILENAME > "/dev/stderr"
  262.     next
  263.     }
  264.     if (Debug > 1) {
  265.     if (Debug > 2)
  266.         printf "Field value: <%s>  Line: %s\n",Val,$0 > "/dev/stderr"
  267.     else
  268.         printf "Field value: <%s>\n",Val,$0 > "/dev/stderr"
  269.     }
  270.     if (Freq)
  271.     doFreq(Val)
  272.     else if (RunLen)
  273.     doRunLen(Val,0,NR)
  274.     else
  275.     doSum(Val)
  276. }
  277.  
  278. # Globals modified: fCount[], savedValues[]
  279. function doFreq(Val,  sVal) {
  280.     if (origDict)
  281.     sVal = origCase ? Val : tolower(Val)
  282.     if (Dictionary)
  283.     Val = Dictionize(Val)
  284.     if (saveValue && !origDict)    # if origCase only
  285.     sVal = Val
  286.     if (pattern != "" && Val !~ pattern ||
  287.     notPattern != "" && Val ~ notPattern)
  288.     next
  289.     if (IGNORECASE)
  290.     Val = tolower(Val)
  291.     if (Debug && (IGNORECASE || Dictionary))
  292.     printf "Modified value: <%s>\n",Val > "/dev/stderr"
  293.     fCount[Val]++
  294.     if (saveValue && !(Val in SavedValues))
  295.     savedValues[Val] = sVal
  296. }
  297.  
  298. # Call with endRun true to end a run w/o starting another.
  299. # Call with endRun and cleanup true after processing all lines, to end final
  300. # run.
  301. function doRunLen(Val,endRun,line) {
  302.     if (runCount) {
  303.     if (lastValue == Val && !endRun)    # Continuing a run
  304.         runCount++
  305.     else {        # At the end of a run
  306.         sum += runCount
  307.         NumValues++
  308.         if (Running)
  309.         TailPrint(sprintf("%d %s",runCount,lastValue))
  310.         if (MinMax) {
  311.         if (MinSum == "" || runCount < MinSum) {
  312.             MinSum = runCount
  313.             MinLine = lastValue
  314.             MinNR = line-1
  315.         }
  316.         if (runCount > MaxSum) {
  317.             MaxSum = runCount
  318.             MaxLine = lastValue
  319.             MaxNR = line-1
  320.         }
  321.         }
  322.         if (endRun)
  323.         runCount = 0
  324.         else {
  325.         lastValue = Val
  326.         runCount = 1
  327.         }
  328.     }
  329.     }
  330.     else {    # start a new run; no previous run
  331.     lastValue = Val
  332.     runCount = 1
  333.     }
  334. }
  335.  
  336. function doSum(Val,  numPart) {
  337.     if (DoTime) {
  338.     if (Val ~ /[^-0-9:.]/) {
  339.         printf "Bad time field on line %d of file %s: %s\n",FNR,FILENAME,
  340.         Val > "/dev/stderr"
  341.         next
  342.     }
  343.     NField = split(Val,Fields,":")
  344.     # Time fields are numbered from right to left starting with 1
  345.     for (i = 1; i <= NField; i++)
  346.         TimeSums[NField - i + 1] += Fields[i]
  347.     if (NField == 3)
  348.         DoHours = 1
  349.     }
  350.     else {
  351.     if (StripNum) {
  352.         if (match(Val,numPat))
  353.         Val = substr(Val,RSTART,RLENGTH)
  354.         else
  355.         Val = ""
  356.         if (Debug > 1)
  357.         printf "Start of number: %d  Length: %d\n",
  358.         RSTART,RLENGTH > "/dev/stderr"
  359.     }
  360.     numPart = ""
  361.     if (match(Val,/^-?([0-9]+\.?[0-9]*|\.[0-9]+)/)) {
  362.         numPart = substr(Val,1,RLENGTH)
  363.         Val = substr(Val,RLENGTH+1)
  364.     }
  365.     if (Metric && Val in metricChars) {
  366.         numPart *= metricChars[Val]
  367.         Val = substr(Val,2)
  368.     }
  369.     if (Val != "") {
  370.         printf "Bad value field on line %d of file %s: %s\n==> %s\n",
  371.         FNR,FILENAME,Val,$0 > "/dev/stderr"
  372.         next
  373.     }
  374.     sum += numPart
  375.     }
  376.     NumValues++
  377.     if (Running) {
  378.     # Some utilities print e.g. 79 columns, space padded, which will wrap
  379.     # when prefixed with running total.
  380.     sub("[ \t]+$","")
  381.     TailPrint(sprintf("%s\t%s",FormatTotal(),$0))
  382.     }
  383.     if (MinMax) {
  384.     NumSum = NumTotal()
  385.     if (NumSum < MinSum) {
  386.         MinSum = NumSum
  387.         MinLine = $0
  388.         MinNR = NR
  389.     }
  390.     if (NumSum > MaxSum) {
  391.         MaxSum = NumSum
  392.         MaxLine = $0
  393.         MaxNR = NR
  394.     }
  395.     }
  396. }
  397.  
  398. END {
  399.     doRunLen("",1,NR+1)     # pretend we're at last line + 1
  400.     if (Err != "")
  401.     exit Err
  402.     if (Freq) {
  403.     HeadPrint(eInfo)
  404.     doFreqRep(fCount)
  405.     }
  406.     else {
  407.     TailPrint(sprintf("%s%s","n" in Options ? "" : "Total: ",FormatTotal()))
  408.     if (MinMax) {
  409.         TailPrint("Minimum " (RunLen ? "run" : "sum") " of " \
  410.         FormatVal(MinSum) " reached " \
  411.         (MinNR ? (sprintf("on line %d:\n%s",MinNR,MinLine)) : \
  412.         "before processing"))
  413.         TailPrint("Maximum " (RunLen ? "run" : "sum") " of " \
  414.         FormatVal(MaxSum) " reached " \
  415.         (MaxNR ? (sprintf("on line %d:\n%s",MaxNR,MaxLine)) : \
  416.         "before processing"))
  417.     }
  418.     if (Average)
  419.         TailPrint(sprintf(\
  420.         "Average over %d values: %s",NumValues,FormatTotal(NumValues)))
  421.     TailFlush()
  422.     }
  423.     exit 0
  424. }
  425.  
  426. function doFreqRep(count,  
  427. tot,countWidth,Pct,Header,pattern,notPattern,format,nLine,j,word) {
  428.     if (pl_sing)
  429.     for (i in count)
  430.         if (i ~ /s$/ && (j = substr(i,1,length(i)-1)) in count) {
  431.         count[j "[s]"] = count[i] + count[j]
  432.         delete count[i]
  433.         delete count[j]
  434.         }
  435.     nLine = qsortArbIndByValue(count,k)
  436.     for (i in count)
  437.     tot += count[i]
  438.     countWidth = length(sprintf("%d",count[k[nLine]]))
  439.     if (Header = !("H" in Options))
  440.     countWidth = max(countWidth,1)
  441.     if (Pct = "%" in Options)
  442.     format = "%" countWidth "s %6s %s"
  443.     else
  444.     format = "%" countWidth "s %s"
  445.     if (MinMax)
  446.     HeadPrint(sprintf("Minimum count: %d   Maximum count: %d\n",
  447.     count[k[1]],count[k[nLine]]))
  448.     if (Average)
  449.     HeadPrint(sprintf("Average: %.2f",tot/nLine))
  450.     if (Header)
  451.     HeadPrint(\
  452.     sprintf(format "    (TOTAL: " tot "; " nLine " different values)",
  453.     "#",Pct ? "%" : "Word","Word"))
  454.     for (i = nLine; i >= 1; i--) {
  455.     ind = k[i]
  456.     word = saveValue ? savedValues[ind] : ind
  457.     if (minCount && count[ind] < minCount)
  458.         return
  459.     if (Pct) {
  460.         if (!HeadPrint(sprintf(format,count[ind],
  461.         sprintf("%.2f",count[ind]/tot*100),word)))
  462.         return
  463.     }
  464.     else
  465.         if (!HeadPrint(sprintf(format,count[ind],word)))
  466.         return
  467.     }
  468. }
  469.  
  470. function NumTotal() {
  471.     if (DoTime)
  472.     return TimeSums[1] + TimeSums[2]*60 + TimeSums[3]*3600
  473.     else
  474.     return sum
  475. }
  476.  
  477. function FormatVal(Tot) {
  478.     if (DoTime) {
  479.     if (DoHours)
  480.         return sec2dhms2(Tot)
  481.     else
  482.         return sprintf("%d:%02d",Tot / 60,Tot % 60)
  483.     }
  484.     else if (Eng)
  485.     return i2emet(Tot,maxDig,Pow2,0)
  486.     else
  487.     return sprintf(numFormat,Tot)
  488. }
  489.  
  490. function FormatTotal(Div,  Tot) {
  491.     Tot = NumTotal()
  492.     if (Div)
  493.     Tot = Tot / Div
  494.     return FormatVal(Tot)
  495. }
  496.  
  497. # Converts Seconds to the form [[[<days>d]hh:]mm:]ss
  498. function sec2dhms2(Seconds,  Days,Hours,Minutes,Time) {
  499.     Days = int(Seconds / 86400)
  500.     Seconds %= 86400
  501.     Hours = int(Seconds / 3600)
  502.     Seconds %= 3600
  503.     Minutes = int(Seconds / 60)
  504.     Seconds %= 60
  505.     if (Days)
  506.     Time = Days "d "
  507.     if (Time || Hours)
  508.     Time = Time sprintf("%02d",Hours) ":"
  509.     if (Time || Minutes)
  510.     Time = Time sprintf("%02d",Minutes) ":"
  511.     Time = Time sprintf("%02d",Seconds)
  512.     return Time
  513. }
  514.  
  515. function Dictionize(s) {
  516.     gsub(/[^a-zA-Z0-9 \t]|^[ \t]+|[ \t]+$/,"",s)
  517.     gsub(/[ \t][ \t]+/," ",s)
  518.     return s
  519. }
  520.  
  521. # Expand tabs in Line
  522. function TabEx(Line,  Segs,i,Num,S) {
  523.     Num = split(Line,Segs,"\t")
  524.     Line = ""
  525.     for (i = 1; i < Num; i++) {
  526.     S = Segs[i]
  527.     Line = Line S substr("        ",length(S) % 8 + 1)
  528.     }
  529.     return Line Segs[Num]
  530. }
  531.  
  532. ### Begin qsort routines
  533.  
  534. # Arr[] is an array of values with arbitrary indices.
  535. # k[] is returned with numeric indices 1..n.
  536. # The values in k[] are the indices of Arr[],
  537. # ordered so that if Arr[] is stepped through
  538. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  539. # through in order of the values of its elements.
  540. # The return value is the number of elements in the arrays (n).
  541. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  542.     ElNum = 0
  543.     for (ArrInd in Arr)
  544.     k[++ElNum] = ArrInd
  545.     qsortSegment(Arr,k,1,ElNum)
  546.     return ElNum
  547. }
  548.  
  549. # Sort a segment of an array.
  550. # Arr[] contains data with arbitrary indices.
  551. # k[] has indices 1..nelem, with the indices of arr[] as values.
  552. # This function sorts the elements of arr that are pointed to by
  553. # k[start..end], swapping the values of elements of k[] so that
  554. # when this function returns arr[k[start..end]] will be in order.
  555. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  556.     # handle two-element case explicitly for a tiny speedup
  557.     if ((end - start) == 1) {
  558.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  559.         k[start] = tmpe
  560.         k[end] = tmps
  561.     }
  562.     return
  563.     }
  564.     # Make sure comparisons act on these as numbers
  565.     left = start+0
  566.     right = end+0
  567.     sepval = Arr[k[int((left + right) / 2)]]
  568.     # Make every element <= sepval be to the left of every element > sepval
  569.     while (left < right) {
  570.     while (Arr[k[left]] < sepval)
  571.         left++
  572.     while (Arr[k[right]] > sepval)
  573.         right--
  574.     if (left < right) {
  575.         tmp = k[left]
  576.         k[left++] = k[right]
  577.         k[right--] = tmp
  578.     }
  579.     }
  580.     if (left == right)
  581.     if (Arr[k[left]] < sepval)
  582.         left++
  583.     else
  584.         right--
  585.     if (start < right)
  586.     qsortSegment(Arr,k,start,right)
  587.     if (left < end)
  588.     qsortSegment(Arr,k,left,end)
  589. }
  590.  
  591. # Arr[] is an array of values with arbitrary indices.
  592. # k[] is returned with numeric indices 1..n.
  593. # The values in k are the indices of Arr[],
  594. # ordered so that if Arr[] is stepped through
  595. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  596. # through in order of the values of its indices.
  597. # The return value is the number of elements in the arrays (n).
  598. # If the indexes are numeric, Numeric should be true, so that they can be
  599. # compared as such rather than as strings.  Numeric indexes do not have to be
  600. # contiguous.
  601. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  602.     ElNum = 0
  603.     if (Numeric)
  604.     # Indexes do not preserve numeric type, so must be forced
  605.     for (ArrInd in Arr)
  606.         k[++ElNum] = ArrInd+0
  607.     else
  608.     for (ArrInd in Arr)
  609.         k[++ElNum] = ArrInd
  610.     qsortNumIndByValue(k,1,ElNum)
  611.     return ElNum
  612. }
  613.  
  614. # Arr is an array of elements with contiguous numeric indexes to be sorted
  615. # by value.
  616. # start and end are the starting and ending indexes of the range to be sorted.
  617. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  618.     # handle two-element case explicitly for a tiny speedup
  619.     if ((start - end) == 1) {
  620.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  621.         Arr[start] = tmpe
  622.         Arr[end] = tmps
  623.     }
  624.     return
  625.     }
  626.     left = start+0
  627.     right = end+0
  628.     sepval = Arr[int((left + right) / 2)]
  629.     while (left < right) {
  630.     while (Arr[left] < sepval)
  631.         left++
  632.     while (Arr[right] > sepval)
  633.         right--
  634.     if (left <= right) {
  635.         tmp = Arr[left]
  636.         Arr[left++] = Arr[right]
  637.         Arr[right--] = tmp
  638.     }
  639.     }
  640.     if (start < right)
  641.     qsortNumIndByValue(Arr,start,right)
  642.     if (left < end)
  643.     qsortNumIndByValue(Arr,left,end)
  644. }
  645.  
  646. ### End qsort routines
  647. ### Begin min,max,In routines
  648.  
  649. function min(a,b) {
  650.     if (a < b)
  651.     return a
  652.     else
  653.     return b
  654. }
  655.  
  656. function max(a,b) {
  657.     if (a > b)
  658.     return a
  659.     else
  660.     return b
  661. }
  662.  
  663. function In(Val,Min,Max) {
  664.     return (Min <= Val && Val <= Max)
  665. }
  666.  
  667. # Return (in Ind) the indices of the elements with the smallest value in A.
  668. # The smallest value is returned as the function value.
  669. # If there are no elements in A, null is returned.
  670. function arrMin(A,Ind,  i,min) {
  671.     for (i in A)
  672.     if (min == "" || A[i] < min) {
  673.         DeleteAll(Ind)
  674.         min = A[i]
  675.         Ind[i]
  676.     }
  677.     else if (A[i] == min)
  678.         Ind[i]
  679.     return min
  680. }
  681.  
  682. ### End min,max,In routines
  683. ### Start of ProcArgs library
  684. # @(#) ProcArgs 1.11 96/12/08
  685. # 92/02/29 john h. dubois iii (john@armory.com)
  686. # 93/07/18 Added "#" arg type
  687. # 93/09/26 Do not count -h against MinArgs
  688. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  689. #          Removed meaning of "+" or "-" by itself.
  690. # 94/03/08 Added & option and *()< option types.
  691. # 94/04/02 Added NoRCopt to Opts()
  692. # 94/06/11 Mark numeric variables as such.
  693. # 94/07/08 Opts(): Do not require any args if h option is given.
  694. # 95/01/22 Record options given more than once.  Record option num in argv.
  695. # 95/06/08 Added ExclusiveOptions().
  696. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  697. #          Expand $VARNAME at the start of its filenames.
  698. #          Let varname=0 and -option- turn off an option.
  699. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  700. #          of the vars should be searched for in the environment.
  701. #          Check for duplicate rcfiles.
  702. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  703. #          now return various negatives values on error, not just -1, and
  704. #          Opts() may set Err to various positive values, not just 1.
  705. #          Added AllowUnrecOpt.
  706. # 96/05/23 Check type given for & option
  707. # 96/06/15 Re-port to awk
  708. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  709. #          used by other functions.
  710. # 96/10/15 Added OptChars
  711. # 96/11/01 Added exOpts arg to Opts()
  712. # 96/11/16 Added ; type
  713. # 96/12/08 Added Opt2Set() & Opt2Sets()
  714. # 96/12/27 Added CmdLineOpt()
  715.  
  716. # optlist is a string which contains all of the possible command line options.
  717. # A character followed by certain characters indicates that the option takes
  718. # an argument, with type as follows:
  719. # :    String argument
  720. # ;    Non-empty string argument
  721. # *    Floating point argument
  722. # (    Non-negative floating point argument
  723. # )    Positive floating point argument
  724. # #    Integer argument
  725. # <    Non-negative integer argument
  726. # >    Positive integer argument
  727. # The only difference the type of argument makes is in the runtime argument
  728. # error checking that is done.
  729.  
  730. # The & option is a special case used to get numeric options without the
  731. # user having to give an option character.  It is shorthand for [-+.0-9].
  732. # If & is included in optlist and an option string that begins with one of
  733. # these characters is seen, the value given to "&" will include the first
  734. # char of the option.  & must be followed by a type character other than ":"
  735. # or ";".
  736. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  737.  
  738. # Strings in argv[] which begin with "-" or "+" are taken to be
  739. # strings of options, except that a string which consists solely of "-"
  740. # or "+" is taken to be a non-option string; like other non-option strings,
  741. # it stops the scanning of argv and is left in argv[].
  742. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  743. # If an option takes an argument, the argument may either immediately
  744. # follow it or be given separately.
  745. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  746. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  747. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  748. # this feature had a flaw that caused problems in some cases.  See the OptChars
  749. # parameter to explicitly set the option-specifier characters.
  750.  
  751. # If an option that does not take an argument is given,
  752. # an index with its name is created in Options and its value is set to the
  753. # number of times it occurs in argv[].
  754.  
  755. # If an option that does take an argument is given, an index with its name is
  756. # created in Options and its value is set to the value of the argument given
  757. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  758. # If an option that takes an argument is given more than once,
  759. # Options[option-name,"count"] is incremented, and the value is assigned to
  760. # the index (option-name,instance) where instance is 2 for the second occurance
  761. # of the option, etc.
  762. # In other words, the first time an option with a value is encountered, the
  763. # value is assigned to an index consisting only of its name; for any further
  764. # occurances of the option, the value index has an extra (count) dimension.
  765.  
  766. # The sequence number for each option found in argv[] is stored in
  767. # Options[option-name,"num",instance], where instance is 1 for the first
  768. # occurance of the option, etc.  The sequence number starts at 1 and is
  769. # incremented for each option, both those that have a value and those that
  770. # do not.  Options set from a config file have a value of 0 assigned to this.
  771.  
  772. # Options and their arguments are deleted from argv.
  773. # Note that this means that there may be gaps left in the indices of argv[].
  774. # If compress is nonzero, argv[] is packed by moving its elements so that
  775. # they have contiguous integer indices starting with 0.
  776. # Option processing will stop with the first unrecognized option, just as
  777. # though -- was given except that unlike -- the unrecognized option will not be
  778. # removed from ARGV[].  Normally, an error value is returned in this case.
  779. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  780. # be found, so the number of remaining arguments is returned instead.
  781. # If OptChars is not a null string, it is the set of characters that indicate
  782. # that an argument is an option string if the string begins with one of the
  783. # characters.  A string consisting solely of two of the same option-indicator
  784. # characters stops the scanning of argv[].  The default is "-+".
  785. # argv[0] is not examined.
  786. # The number of arguments left in argc is returned.
  787. # If an error occurs, the global string OptErr is set to an error message
  788. # and a negative value is returned.
  789. # Current error values:
  790. # -1: option that required an argument did not get it.
  791. # -2: argument of incorrect type supplied for an option.
  792. # -3: unrecognized (invalid) option.
  793. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  794. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  795. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  796. {
  797. # ArgNum is the index of the argument being processed.
  798. # ArgsLeft is the number of arguments left in argv.
  799. # Arg is the argument being processed.
  800. # ArgLen is the length of the argument being processed.
  801. # ArgInd is the position of the character in Arg being processed.
  802. # Option is the character in Arg being processed.
  803. # Pos is the position in OptList of the option being processed.
  804. # NumOpt is true if a numeric option may be given.
  805.     ArgsLeft = argc
  806.     NumOpt = index(OptList,"&")
  807.     OptionNum = 0
  808.     if (OptChars == "")
  809.     OptChars = "-+"
  810.     while (OptChars != "") {
  811.     c = substr(OptChars,1,1)
  812.     OptChars = substr(OptChars,2)
  813.     OptCharSet[c]
  814.     OptTerm[c c]
  815.     }
  816.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  817.     Arg = argv[ArgNum]
  818.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  819.         break    # Not an option; quit
  820.     if (Arg in OptTerm) {
  821.         delete argv[ArgNum]
  822.         ArgsLeft--
  823.         break
  824.     }
  825.     ArgLen = length(Arg)
  826.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  827.         Option = substr(Arg,ArgInd,1)
  828.         if (NumOpt && Option ~ /[-+.0-9]/) {
  829.         # If this option is a numeric option, make its flag be & and
  830.         # its option string flag position be the position of & in
  831.         # the option string.
  832.         Option = "&"
  833.         Pos = NumOpt
  834.         # Prefix Arg with a char so that ArgInd will point to the
  835.         # first char of the numeric option.
  836.         Arg = "&" Arg
  837.         ArgLen++
  838.         }
  839.         # Find position of flag in option string, to get its type (if any).
  840.         # Disallow & as literal flag.
  841.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  842.         if (AllowUnrecOpt) {
  843.             Escape = 1
  844.             break
  845.         }
  846.         else {
  847.             OptErr = "Invalid option: " specGiven Option
  848.             return -3
  849.         }
  850.         }
  851.  
  852.         # Find what the value of the option will be if it takes one.
  853.         # NeedNextOpt is true if the option specifier is the last char of
  854.         # this arg, which means that if the option requires a value it is
  855.         # the next arg.
  856.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  857.         if (GotValue = ArgNum + 1 < argc)
  858.             Value = argv[ArgNum+1]
  859.         }
  860.         else {    # Value is included with option
  861.         Value = substr(Arg,ArgInd + 1)
  862.         GotValue = 1
  863.         }
  864.  
  865.         if (HadValue = AssignVal(Option,Value,Options,
  866.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  867.         specGiven)) {
  868.         if (HadValue < 0)    # error occured
  869.             return HadValue
  870.         if (HadValue == 2)
  871.             ArgInd++    # Account for the single-char value we used.
  872.         else {
  873.             if (NeedNextOpt) {    # option took next arg as value
  874.             delete argv[++ArgNum]
  875.             ArgsLeft--
  876.             }
  877.             break    # This option has been used up
  878.         }
  879.         }
  880.     }
  881.     if (Escape)
  882.         break
  883.     # Do not delete arg until after processing of it, so that if it is not
  884.     # recognized it can be left in ARGV[].
  885.     delete argv[ArgNum]
  886.     ArgsLeft--
  887.     }
  888.     if (compress != 0) {
  889.     dest = 1
  890.     src = argc - ArgsLeft + 1
  891.     for (count = ArgsLeft - 1; count; count--) {
  892.         ARGV[dest] = ARGV[src]
  893.         dest++
  894.         src++
  895.     }
  896.     }
  897.     return ArgsLeft
  898. }
  899.  
  900. # Assignment to values in Options[] occurs only in this function.
  901. # Option: Option specifier character.
  902. # Value: Value to be assigned to option, if it takes a value.
  903. # Options[]: Options array to return values in.
  904. # ArgType: Argument type specifier character.
  905. # GotValue: Whether any value is available to be assigned to this option.
  906. # Name: Name of option being processed.
  907. # OptionNum: Number of this option (starting with 1) if set in argv[],
  908. #     or 0 if it was given in a config file or in the environment.
  909. # SingleOpt: true if the value (if any) that is available for this option was
  910. #     given as part of the same command line arg as the option.  Used only for
  911. #     options from the command line.
  912. # specGiven is the option specifier character use, if any (e.g. - or +),
  913. # for use in error messages.
  914. # Global variables: OptErr
  915. # Return value: negative value on error, 0 if option did not require an
  916. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  917. # the arg.
  918. # Current error values:
  919. # -1: Option that required an argument did not get it.
  920. # -2: Value of incorrect type supplied for option.
  921. # -3: Bad type given for option &
  922. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  923. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  924.     # If option takes a value...    [
  925.     NumTypes = "*()#<>]"
  926.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  927.     OptErr = "Bad type given for & option"
  928.     return -3
  929.     }
  930.  
  931.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  932.     if (!GotValue) {
  933.         if (Name != "")
  934.         OptErr = "Variable requires a value -- " Name
  935.         else
  936.         OptErr = "option requires an argument -- " Option
  937.         return -1
  938.     }
  939.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  940.         OptErr = Err
  941.         return -2
  942.     }
  943.     # Mark this as a numeric variable; will be propogated to Options[] val.
  944.     if (ArgType != ":" && ArgType != ";")
  945.         Value += 0
  946.     if ((Instance = ++Options[Option,"count"]) > 1)
  947.         Options[Option,Instance] = Value
  948.     else
  949.         Options[Option] = Value
  950.     }
  951.     # If this is an environ or rcfile assignment & it was given a value...
  952.     else if (!OptionNum && Value != "") {
  953.     UsedValue = 1
  954.     # If the value is "0" or "-" and this is the first instance of it,
  955.     # do not set Options[Option]; this allows an assignment in an rcfile to
  956.     # turn off an option (for the simple "Option in Options" test) in such
  957.     # a way that it cannot be turned on in a later file.
  958.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  959.         Instance = 1
  960.     else
  961.         Instance = ++Options[Option]
  962.     # Save the value even though this is a flag
  963.     Options[Option,Instance] = Value
  964.     }
  965.     # If this is a command line flag and has a - following it in the same arg,
  966.     # it is being turned off.
  967.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  968.     UsedValue = 2
  969.     if (Option in Options)
  970.         Instance = ++Options[Option]
  971.     else
  972.         Instance = 1
  973.     Options[Option,Instance]
  974.     }
  975.     # If this is a flag assignment without a value, increment the count for the
  976.     # flag unless it was turned off.  The indicator for a flag being turned off
  977.     # is that the flag index has not been set in Options[] but it has an
  978.     # instance count.
  979.     else if (Option in Options || !((Option,1) in Options))
  980.     # Increment number of times this flag seen; will inc null value to 1
  981.     Instance = ++Options[Option]
  982.     Options[Option,"num",Instance] = OptionNum
  983.     return UsedValue
  984. }
  985.  
  986. # Option is the option letter
  987. # Value is the value being assigned
  988. # Name is the var name of the option, if any
  989. # ArgType is one of:
  990. # :    String argument
  991. # ;    Non-null string argument
  992. # *    Floating point argument
  993. # (    Non-negative floating point argument
  994. # )    Positive floating point argument
  995. # #    Integer argument
  996. # <    Non-negative integer argument
  997. # >    Positive integer argument
  998. # specGiven is the option specifier character use, if any (e.g. - or +),
  999. # for use in error messages.
  1000. # Returns null on success, err string on error
  1001. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  1002.     if (ArgType == ":")
  1003.     return ""
  1004.     if (ArgType == ";") {
  1005.     if (Value == "")
  1006.         Err = "must be a non-empty string"
  1007.     }
  1008.     # A number begins with optional + or -, and is followed by a string of
  1009.     # digits or a decimal with digits before it, after it, or both
  1010.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  1011.     Err = "must be a number"
  1012.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  1013.     Err = "may not include a fraction"
  1014.     else if (ArgType ~ "[()<>]" && Value < 0)
  1015.     Err = "may not be negative"
  1016.     # (
  1017.     else if (ArgType ~ "[)>]" && Value == 0)
  1018.     Err = "must be a positive number"
  1019.     if (Err != "") {
  1020.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1021.     if (Name != "")
  1022.         return ErrStr "variable " substr(Name,1,1) " " Err
  1023.     else {
  1024.         if (Option == "&")
  1025.         Option = Value
  1026.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1027.     }
  1028.     }
  1029.     else
  1030.     return ""
  1031. }
  1032.  
  1033. # Note: only the above functions are needed by ProcArgs.
  1034. # The rest of these functions call ProcArgs() and also do other
  1035. # option-processing stuff.
  1036.  
  1037. # Opts: Process command line arguments.
  1038. # Opts processes command line arguments using ProcArgs()
  1039. # and checks for errors.  If an error occurs, a message is printed
  1040. # and the program is exited.
  1041. #
  1042. # Input variables:
  1043. # Name is the name of the program, for error messages.
  1044. # Usage is a usage message, for error messages.
  1045. # OptList the option description string, as used by ProcArgs().
  1046. # MinArgs is the minimum number of non-option arguments that this
  1047. # program should have, non including ARGV[0] and +h.
  1048. # If the program does not require any non-option arguments,
  1049. # MinArgs should be omitted or given as 0.
  1050. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1051. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1052. # by the value of the environment variable HOME.  If a filename begins with
  1053. # $, the part from the character after the $ up until (but not including)
  1054. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1055. # environment; if found its value will be substituted, if not the filename will
  1056. # be discarded.
  1057. # rcfiles are read in the order given.
  1058. # Values given in them will not override values given on the command line,
  1059. # and values given in later files will not override those set in earlier
  1060. # files, because AssignVal() will store each with a different instance index.
  1061. # The first instance of each variable, either on the command line or in an
  1062. # rcfile, will be stored with no instance index, and this is the value
  1063. # normally used by programs that call this function.
  1064. # VarNames is a comma-separated list of variable names to map to options,
  1065. # in the same order as the options are given in OptList.
  1066. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1067. # searched for in the environment.  If set to -1, all values will be searched
  1068. # for in the environment.  Values given in the environment will override
  1069. # those given in the rcfiles but not those given on the command line.
  1070. # NoRCopt, if given, is an additional letter option that if given on the
  1071. # command line prevents the rcfiles from being read.
  1072. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1073. # ExclusiveOptions() for a description of exOpts.
  1074. # Special options:
  1075. # If x is made an option and is given, some debugging info is output.
  1076. # h is assumed to be the help option.
  1077.  
  1078. # Global variables:
  1079. # The command line arguments are taken from ARGV[].
  1080. # The arguments that are option specifiers and values are removed from
  1081. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1082. # The number of elements in ARGV[] should be in ARGC.
  1083. # After processing, ARGC is set to the number of elements left in ARGV[].
  1084. # The option values are put in Options[].
  1085. # On error, Err is set to a positive integer value so it can be checked for in
  1086. # an END block.
  1087. # Return value: The number of elements left in ARGV is returned.
  1088. # Must keep OptErr global since it may be set by InitOpts().
  1089. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1090. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1091.     if (MinArgs == "")
  1092.     MinArgs = 0
  1093.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1094.     optChars)
  1095.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1096.     if (ArgsLeft >= 0) {
  1097.         OptErr = "Not enough arguments"
  1098.         Err = 4
  1099.     }
  1100.     else
  1101.         Err = -ArgsLeft
  1102.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1103.     Name,OptErr,Usage > "/dev/stderr"
  1104.     exit 1
  1105.     }
  1106.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1107.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1108.     {
  1109.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1110.     Err = -e
  1111.     exit 1
  1112.     }
  1113.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1114.     {
  1115.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1116.     Err = 1
  1117.     exit 1
  1118.     }
  1119.     return ArgsLeft
  1120. }
  1121.  
  1122. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1123. # <variable-name><assignment-char><value>.
  1124. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1125. # line and whitespace between the variable name and the assignment character) 
  1126. # is stripped.  Lines that do not contain an assignment operator or which
  1127. # contain a null variable name are ignored, other than possibly being noted in
  1128. # the return value.  If more than one assignment is made to a variable, the
  1129. # first assignment is used.
  1130. # Input variables:
  1131. # File is the file to read.
  1132. # Comment is the line-comment character.  If it is found as the first non-
  1133. #     whitespace character on a line, the line is ignored.
  1134. # Assign is the assignment string.  The first instance of Assign on a line
  1135. #     separates the variable name from its value.
  1136. # If StripWhite is true, whitespace around the value (whitespace between the
  1137. #     assignment char and trailing whitespace on the line) is stripped.
  1138. # VarPat is a pattern that variable names must match.  
  1139. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1140. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1141. #     a line; no assignment operator is needed.  These variables are set in
  1142. #     the output array with a null value.  Lines containing nothing but
  1143. #     whitespace are still ignored.
  1144. # Output variables:
  1145. # Values[] contains the assignments, with the indexes being the variable names
  1146. #     and the values being the assigned values.
  1147. # Lines[] contains the line number that each variable occured on.  A flag set
  1148. #     is record by giving it an index in Lines[] but not in Values[].
  1149. # Return value:
  1150. # If any errors occur, a string consisting of descriptions of the errors
  1151. # separated by newlines is returned.  In no case will the string start with a
  1152. # numeric value.  If no errors occur,  the number of lines read is returned.
  1153. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1154. FlagsOK,
  1155. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1156.     if (Comment != "")
  1157.     Comment = "^" Comment
  1158.     AssignLen = length(Assign)
  1159.     if (VarPat == "")
  1160.     VarPat = "."    # null varname not allowed
  1161.     while ((Status = (getline Line < File)) == 1) {
  1162.     LineNum++
  1163.     sub("^[ \t]+","",Line)
  1164.     if (Line == "")        # blank line
  1165.         continue
  1166.     if (Comment != "" && Line ~ Comment)
  1167.         continue
  1168.     if (Pos = index(Line,Assign)) {
  1169.         Var = substr(Line,1,Pos-1)
  1170.         Val = substr(Line,Pos+AssignLen)
  1171.         if (StripWhite) {
  1172.         sub("^[ \t]+","",Val)
  1173.         sub("[ \t]+$","",Val)
  1174.         }
  1175.     }
  1176.     else {
  1177.         Var = Line    # If no value, var is entire line
  1178.         Val = ""
  1179.     }
  1180.     if (!FlagsOK && Val == "") {
  1181.         Errs = Errs \
  1182.         sprintf("\nBad assignment on line %d of file %s: %s",
  1183.         LineNum,File,Line)
  1184.         continue
  1185.     }
  1186.     sub("[ \t]+$","",Var)
  1187.     if (Var !~ VarPat) {
  1188.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1189.         LineNum,File,Var)
  1190.         continue
  1191.     }
  1192.     if (!(Var in Lines)) {
  1193.         Lines[Var] = LineNum
  1194.         if (Pos)
  1195.         Values[Var] = Val
  1196.     }
  1197.     }
  1198.     if (Status)
  1199.     Errs = Errs "\nCould not read file " File
  1200.     close(File)
  1201.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1202. }
  1203.  
  1204. # Variables:
  1205. # Data is stored in Options[].
  1206. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1207. # Global vars:
  1208. # Sets OptErr.  Uses ENVIRON[].
  1209. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1210. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1211. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1212. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1213.     split("",filesRead,"")    # make awk know this is an array
  1214.     NumVars = split(VarNames,Vars,",")
  1215.     TypesInd = Ret = 0
  1216.     if (EnvSearch == -1)
  1217.     EnvSearch = NumVars
  1218.     for (i = 1; i <= NumVars; i++) {
  1219.     Var = Vars[i]
  1220.     CharOpt = substr(OptList,++TypesInd,1)
  1221.     if (CharOpt ~ "^[:;*()#<>&]$")
  1222.         CharOpt = substr(OptList,++TypesInd,1)
  1223.     Map[Var] = CharOpt
  1224.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1225.     # Do not overwrite entries from environment
  1226.     if (i <= EnvSearch && Var in ENVIRON &&
  1227.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1228.         return Err
  1229.     }
  1230.  
  1231.     numrcFiles = split(rcFiles,fNames,":")
  1232.     for (i = 1; i <= numrcFiles; i++) {
  1233.     rcFile = fNames[i]
  1234.     if (rcFile ~ "^~/")
  1235.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1236.     else if (rcFile ~ /^\$/) {
  1237.         rcFile = substr(rcFile,2)
  1238.         match(rcFile,"^[a-zA-Z0-9_]*")
  1239.         envvar = substr(rcFile,1,RLENGTH)
  1240.         if (envvar in ENVIRON)
  1241.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1242.         else
  1243.         continue
  1244.     }
  1245.     if (rcFile in filesRead)
  1246.         continue
  1247.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1248.     # may be the same
  1249.     filesRead[rcFile]
  1250.     if ("x" in Options)
  1251.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1252.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1253.     if (retStr > 0)
  1254.         READ_RCFILE = 1
  1255.     else if (ret != "") {
  1256.         OptErr = retStr
  1257.         Ret = -1
  1258.     }
  1259.     for (Var in Lines)
  1260.         if (Var in Map) {
  1261.         if ((Err = AssignVal(Map[Var],
  1262.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1263.         Var in Values,Var,0)) < 0)
  1264.             return Err
  1265.         }
  1266.         else {
  1267.         OptErr = sprintf(\
  1268.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1269.         Lines[Var],rcFile)
  1270.         Ret = -1
  1271.         }
  1272.     }
  1273.  
  1274.     if ("x" in Options)
  1275.     for (Var in Map)
  1276.         if (Map[Var] in Options)
  1277.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1278.         "/dev/stderr"
  1279.         else
  1280.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1281.     return Ret
  1282. }
  1283.  
  1284. # OptSets is a semicolon-separated list of sets of option sets.
  1285. # Within a list of option sets, the option sets are separated by commas.  For
  1286. # each set of sets, if any option in one of the sets is in Options[] AND any
  1287. # option in one of the other sets is in Options[], an error string is returned.
  1288. # If no conflicts are found, nothing is returned.
  1289. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1290. # the exclusions presented by the first set of sets (ab,def,g) if:
  1291. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1292. # (a or b is in Options[]) AND (g is in Options) OR
  1293. # (d, e, or f is in Options[]) AND (g is in Options)
  1294. # An error will be returned due to the exclusions presented by the second set
  1295. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1296. # todo: make options given on command line unset options given in config file
  1297. # todo: that they conflict with.
  1298. function ExclusiveOptions(OptSets,Options,
  1299. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1300. SetNum,OSetNum) {
  1301.     NumSetSets = split(OptSets,SetSets,";")
  1302.     # For each set of sets...
  1303.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1304.     # NumSets is the number of sets in this set of sets.
  1305.     NumSets = split(SetSets[SetSet],Sets,",")
  1306.     # For each set in a set of sets except the last...
  1307.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1308.         s1 = Sets[SetNum]
  1309.         L1 = length(s1)
  1310.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1311.         # If any of the options in this set was given, check whether
  1312.         # any of the options in the other sets was given.  Only check
  1313.         # later sets since earlier sets will have already been checked
  1314.         # against this set.
  1315.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1316.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1317.             s2 = Sets[OSetNum]
  1318.             L2 = length(s2)
  1319.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1320.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1321.                 ErrStr = ErrStr "\n"\
  1322.                 sprintf("Cannot give both %s and %s options.",
  1323.                 c1,c2)
  1324.             }
  1325.     }
  1326.     }
  1327.     if (ErrStr != "")
  1328.     return substr(ErrStr,2)
  1329.     return ""
  1330. }
  1331.  
  1332. # The value of each instance of option Opt that occurs in Options[] is made an
  1333. # index of Set[].
  1334. # The return value is the number of instances of Opt in Options.
  1335. function Opt2Set(Options,Opt,Set,  count) {
  1336.     if (!(Opt in Options))
  1337.     return 0
  1338.     Set[Options[Opt]]
  1339.     count = Options[Opt,"count"]
  1340.     for (; count > 1; count--)
  1341.     Set[Options[Opt,count]]
  1342.     return count
  1343. }
  1344.  
  1345. # The value of each instance of option Opt that occurs in Options[] that
  1346. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1347. # Other values are made indexes of Set[].
  1348. # The return value is the number of instances of Opt in Options.
  1349. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1350.     ret = Opt2Set(Options,Opt,aSet)
  1351.     for (value in aSet)
  1352.     if (substr(value,1,1) == "!")
  1353.         nSet[substr(value,2)]
  1354.     else
  1355.         Set[value]
  1356.     return ret
  1357. }
  1358.  
  1359. # Returns true if option Opt was given on the command line.
  1360. function CmdLineOpt(Options,Opt,  i) {
  1361.     for (i = 1; (Opt,"num",i) in Options; i++)
  1362.     if (Options[Opt,"num",i] != 0)
  1363.         return 1
  1364.     return 0
  1365. }
  1366. ### End of ProcArgs library
  1367. ### Begin head-tail routines
  1368.  
  1369. # @(#) HeadTail.awk 97/01/25
  1370. # 95/04/28 Added tail routines.
  1371. # 96/05/09 Added all args to HeadTailInit()
  1372. # 97/01/25 Make HeadPrint understand embedded newlines too.
  1373.  
  1374. # Turn on screen-bounded printing.
  1375. # Current implementation sets global vars LINES, COLUMNS, LINEGAP, and COLGAP.
  1376. # Sets the number of screen lines and rows to Lines and Rows.
  1377. # If -1 is passed for either, turns off bounding in that dimension.
  1378. # If either is not set or 0 is passed for it, its value is taken from the
  1379. # environment, or if not set there, from terminfo, or if not set there, from
  1380. # the defaults (24 and 80).
  1381. # By default, the other functions in this library leave a "grace space" of
  1382. # 1 column and 1 line.  If LineGap or ColGap is passed and is a non-negative
  1383. # value, the line gap is set to it.
  1384. function HeadTailInit(Lines,Cols,LineGap,ColGap,  Cmd) {
  1385.     # tput will use values in environment, but we want to avoid running
  1386.     # it if possible.
  1387.     if (Cols > 0)
  1388.     COLUMNS = Cols
  1389.     else if (!Cols)
  1390.     if ("COLUMNS" in ENVIRON)
  1391.         COLUMNS = ENVIRON["COLUMNS"]
  1392.     else {
  1393.         Cmd = "exec tput cols"
  1394.         Cmd | getline COLUMNS
  1395.         close(Cmd)
  1396.         if (COLUMNS == "")
  1397.         COLUMNS = 80
  1398.     }
  1399.     if (Lines > 0)
  1400.     LINES = Lines
  1401.     else if (!Lines)
  1402.     if ("LINES" in ENVIRON)
  1403.         LINES = ENVIRON["LINES"]
  1404.     else {
  1405.         Cmd = "exec tput lines"
  1406.         Cmd | getline LINES
  1407.         close(Cmd)
  1408.         if (LINES == "")
  1409.         LINES = 24
  1410.     }
  1411.     LINEGAP = (LineGap != "" && LineGap >= 0) ? LineGap : 1
  1412.     COLGAP = (ColGap != "" && ColGap >= 0) ? ColGap : 1
  1413. }
  1414.  
  1415. # Do screen-bound printing.
  1416. # If LINES  is >0, the last LINES-LINEGAP lines are kept in a circular buffer.
  1417. # When TailFlush() is called, they are printed.
  1418. # If LINES = 0, all lines are printed immediately.
  1419. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  1420. # it.
  1421. # Global vars: uses LINES & COLUMNS; sets/uses TailPtr;
  1422. # saves lines in TailLines[] from 1..LINES-LINEGAP
  1423. # Embedded newlines split the line into multiple lines; trailing newlines are
  1424. # stripped.  Tabs are expanded to spaces.
  1425. function TailPrint(Line) {
  1426.     if (!LINES)
  1427.     print Line
  1428.     else {
  1429.     if (++TailPtr > (LINES-LINEGAP))
  1430.         TailPtr = 1
  1431.     TailLines[TailPtr] = Line
  1432.     }
  1433. }
  1434.  
  1435. function TailFlush(  NumPrinted,Lines,Line,i,Buffer,PrintLines) {
  1436.     if (!LINES)
  1437.     return
  1438.     NumPrinted = 0
  1439.     PrintLines = LINES-LINEGAP
  1440.     # Since lines may contain multiple lines, we must create a buffer to be
  1441.     # printed by reading line buffer backwards.
  1442.     # Stop when we have copied enough lines, or if we wrap around to the end
  1443.     # and find that the entire line buffer was not used.
  1444.     while (NumPrinted < PrintLines && TailPtr in TailLines) {
  1445.     # Split line into individual lines, then process them last to first
  1446.     Num = split(TailLines[TailPtr],Lines,"\n")
  1447.     for (i = Num; i >= 1; i--) {
  1448.         Line = Lines[i]
  1449.         if (i == Num && Line == "")    # discard trailing newline
  1450.         continue
  1451.         # Put this line at the front of the print buffer
  1452.         if (COLUMNS)
  1453.         Buffer = substr(TabEx(Line),1,COLUMNS - COLGAP) "\n" Buffer
  1454.         else
  1455.         Buffer = Line "\n" Buffer
  1456.         if (++NumPrinted == PrintLines)
  1457.         break
  1458.     }
  1459.     if (!--TailPtr)    # Wrap pointer if neccessary
  1460.         TailPtr = PrintLines
  1461.     }
  1462.     printf "%s",Buffer
  1463. }
  1464.  
  1465. # Do screen-bound printing.
  1466. # If LINES >0, returns 0 when LINES-LINEGAP lines have been printed by
  1467. # HeadPrint().  Otherwise returns 1.
  1468. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  1469. # it.
  1470. # Global vars: uses LINES, COLUMNS, LINEGAP, COLGAP; sets/uses LinesPrinted.
  1471. # Embedded newlines split the line into multiple lines; trailing newlines are
  1472. # stripped.  Tabs are expanded to spaces.
  1473. function HeadPrint(Line,  Num,i,Lines) {
  1474.     # Check first, in case some calls of this function to not check return
  1475.     # value, and in case LINES is 1.
  1476.     if (LINES && LinesPrinted >= (LINES-LINEGAP))
  1477.     return 0
  1478.     Num = split(Line,Lines,"\n")
  1479.     if (Lines[Num] == "")    # discard trailing newline
  1480.     Num--
  1481.     for (i = 1; i <= Num; i++) {
  1482.     Line = Lines[i]
  1483.     if (COLUMNS)
  1484.         print substr(Line,1,COLUMNS - COLGAP)
  1485.     else
  1486.         print Line
  1487.     if (LINES && ++LinesPrinted >= (LINES-LINEGAP))
  1488.         return 0
  1489.     }
  1490.     return 1
  1491. }
  1492.  
  1493. function ColPrint(Line) {
  1494.     if (COLUMNS)
  1495.     print substr(Line,1,COLUMNS - COLGAP)
  1496.     else
  1497.     print Line
  1498.     return 1
  1499. }
  1500.  
  1501. ### End head-tail routines
  1502.  
  1503. # @(#) i2met.awk 1.0 96/02/13
  1504. # jhdiii 96/01/14
  1505. # Convert positive integer value Value to a string at most MaxLen characters
  1506. # long.  This is done by converting the integer to a string of the form n*m,
  1507. # where m is a metric suffix from: K M G
  1508. # If Pow2 is true, then each factor of 1K is taken to be 1024; if it is false,
  1509. # it is taken to be 1000.
  1510. # MaxLen must be between 4 and 9.
  1511. # Value may be any integer from 0..maxint
  1512. # If Units is given, it is the units that Value is passed in, where
  1513. # Units=1 means Value is in K; Units=2 means Value is in M, etc.
  1514. function i2met(Value,MaxLen,Pow2,Units,  Len,Div) {
  1515.     if (Value == 0)
  1516.     return "0"
  1517.     if (!(1 in Suf))
  1518.     split("K,M,G,T,P,E,Z,Y",Suf,",")
  1519.     # In both awk & gawk, integer values that can be represented as
  1520.     # machine integers will be printed as integers.
  1521.     # If value can be printed without modification, return it as it,
  1522.     # but with a multiplier suffix if reqd.
  1523.     if ((Len = length(Value Suf[Units])) <= MaxLen)
  1524.     return Value Suf[Units]
  1525.     MaxLen -= 1        # Leave space for suffix
  1526.     Div = Pow2 ? 1024 : 1000
  1527.     for (Units += 1; Units in Suf; Units++)
  1528.     if (length(int(Value /= Div)) <= MaxLen)
  1529.         break
  1530.     Value = substr(sprintf("%." MaxLen "f",Value),1,MaxLen)
  1531.     if (substr(Value,MaxLen,1) == ".")
  1532.     Value = substr(Value,1,MaxLen-1)
  1533.     return Value Suf[Units]
  1534. }
  1535.  
  1536. # @(#) i2emet.awk 1.0 96/02/13
  1537. # jhdiii 96/01/27
  1538. # Convert numeric value Value to one with the decimal point set according to
  1539. # engineering convention.  In this convention, there is always between 1 and
  1540. # 3 digits before the decimal point.  A metric suffix is attached to retain
  1541. # the original value.
  1542. # If Pow2 is true, then each factor of 1K is taken to be 1024; if it is false,
  1543. # it is taken to be 1000.
  1544. # If Pow2 is true, MaxLen must be >= 5; if Pow2 is false, MaxLen must be >= 4.
  1545. # If Units is given, it is the units that Value is passed in, where
  1546. # Units=0 means Value is in base units; Units=-1 means Value is in milliunits,
  1547. # Units=1 means Value is in kilounits, etc.
  1548. # If NoZeroes is true, trailing zeroes in the fractional part are removed.
  1549. function i2emet(Value,MaxLen,Pow2,Units,NoZeroes,  Len,Factor,i,suf2) {
  1550.     if (Value == 0)
  1551.     return "0"
  1552.     if (!(1 in _Suf)) {
  1553.     _MaxUnit = split("K,M,G,T,P,E,Z,Y",_Suf,",")
  1554.     split("m,u,n,p,f,a,z,y",suf2,",")
  1555.     for (i = 1; i in suf2; i++)
  1556.         _Suf[-i] = suf2[i]
  1557.     }
  1558.     # Make sure awk treats all of these as numbers
  1559.     Factor = (Pow2 ? 1024 : 1000)+0
  1560.     Units += 0
  1561.     Value += 0
  1562.     if (Value < 1)
  1563.     for (; Value < 1 && Units > -_MaxUnit; Value *= Factor)
  1564.         Units--
  1565.     else
  1566.     for (; Value >= Factor && Units < _MaxUnit; Value /= Factor)
  1567.         Units++
  1568.     if (Units)
  1569.     MaxLen -= 1        # Leave space for suffix
  1570.     # Round reasonably carefully
  1571.     fDig = MaxLen-length(int(Value))-1
  1572.     if (fDig > 0)
  1573.     Value = sprintf("%." fDig "f",Value)+0    # Turn it back into a number!
  1574.     else
  1575.     Value = int(Value+0.5)
  1576.     # Rounding may have caused rollover of leading digit, making the result
  1577.     # exceed the allowed range (e.g. 999.6 -> 1000)
  1578.     if (Value >= Factor) {
  1579.     Value /= Factor
  1580.     Units++
  1581.     }
  1582.     if (substr(Value,MaxLen,1) == ".")
  1583.     Value = substr(Value,1,MaxLen-1)    # Get rid of trailing "."
  1584.     else
  1585.     Value = substr(Value,1,MaxLen)
  1586.     if (NoZeroes && Value ~ /\..*0$/)
  1587.     sub(/\.?0+$/,"",Value)
  1588.     return Value _Suf[Units]
  1589. }
  1590.